/* An overview of the state machine from sm-fd.cc.
   Copyright (C) 2022-2023 Free Software Foundation, Inc.
   Contributed by David Malcolm <dmalcolm@redhat.com>.

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

/* Keep this in-sync with sm-fd.cc  */

digraph "fd" {

  /* STATES. */

  /* Start state.  */
  start;

  /* State for a constant file descriptor (>= 0).  */
  constant_fd;

  /* States representing a file descriptor that hasn't yet been
    checked for validity after opening, for three different
    access modes.  */
  unchecked_read_write;
  unchecked_read_only;
  unchecked_write_only;

  /* States for representing a file descriptor that is known to be valid (>=
    0), for three different access modes.  */
  valid_read_write;
  valid_read_only;
  valid_write_only;

  /* State for a file descriptor that is known to be invalid (< 0). */
  invalid;

  /* State for a file descriptor that has been closed.  */
  closed;

  /* States for FDs relating to socket APIs.  */

  /* Result of successful "socket" with SOCK_DGRAM.  */
  new_datagram_socket;
  /* Result of successful "socket" with SOCK_STREAM.  */
  new_stream_socket;
  /* Result of successful "socket" with unknown type.  */
  new_unknown_socket;

  /* The above after a successful call to "bind".  */
  bound_datagram_socket;
  bound_stream_socket;
  bound_unknown_socket;

  /* A bound socket after a successful call to "listen" (stream or unknown).  */
  listening_stream_socket;

  /* (i) the new FD as a result of a succesful call to "accept" on a
      listening socket (via a passive open), or
     (ii) an active socket after a successful call to "connect"
     (via an active open).  */
  connected_stream_socket;

  /* State for a file descriptor that we do not want to track anymore . */
  stop;

  /* TRANSITIONS. */

  /* On "open".  */
  start -> unchecked_read_only [label="on 'X = open(..., O_RDONLY);'"];
  start -> unchecked_write_only [label="on 'X = open(..., O_WRONLY);'"];
  start -> unchecked_read_write [label="on 'X = open(..., ...);'"];

  /* On "creat".  */
  start -> unchecked_write_only [label="on 'X = create(...);'"];

  /* On "close".  */
  start -> closed [label="on 'close(X);'"];
  unchecked_read_write -> closed [label="on 'close(X);'"];
  unchecked_read_only -> closed [label="on 'close(X);'"];
  unchecked_write_only -> closed [label="on 'close(X);'"];
  valid_read_write -> closed [label="on 'close(X);'"];
  valid_read_only -> closed [label="on 'close(X);'"];
  valid_write_only -> closed [label="on 'close(X);'"];
  constant_fd -> closed [label="on 'close(X);'"];
  new_datagram_socket -> closed [label="on 'close(X);'"];
  new_stream_socket -> closed [label="on 'close(X);'"];
  new_unknown_socket -> closed [label="on 'close(X);'"];
  bound_datagram_socket -> closed [label="on 'close(X);'"];
  bound_stream_socket -> closed [label="on 'close(X);'"];
  bound_unknown_socket -> closed [label="on 'close(X);'"];
  listening_stream_socket -> closed [label="on 'close(X);'"];
  connected_stream_socket -> closed [label="on 'close(X);'"];
  closed -> stop [label="on 'close(X);':\nWarn('double close')"];

  /* On "read".  */
  closed -> closed [label="on 'read(X);':\nWarn('use after close')"];
  unchecked_read_write -> unchecked_read_write [label="on 'read(X);:\nWarn('use without check')'"];
  unchecked_read_only -> unchecked_read_only [label="on 'read(X);:\nWarn('use without check')'"];
  unchecked_write_only -> unchecked_write_only [label="on 'read(X);:\nWarn('use without check')'"];
  valid_write_only -> valid_write_only [label="on 'read(X);:\nWarn('access mode mismatch')'"];

  /* On "write".  */
  closed -> closed [label="on 'write(X);':\nWarn('use after close')"];
  unchecked_read_write -> unchecked_read_write [label="on 'write(X);:\nWarn('use without check')'"];
  unchecked_read_only -> unchecked_read_only [label="on 'write(X);:\nWarn('use without check')'"];
  unchecked_write_only -> unchecked_write_only [label="on 'write(X);:\nWarn('use without check')'"];
  valid_read_only -> valid_read_only [label="on 'write(X);:\nWarn('access mode mismatch')'"];

  /* On "dup".  */
  closed -> closed [label="on 'dup(X);':\nWarn('use after close')"];
  /* plus stuff for the new fd.  */

  /* On "pipe".  */
  start -> valid_read_write [label="when 'pipe()' succeeds"];

  /* On "socket".  */
  start -> new_datagram_socket [label="when 'socket(..., SOCK_DGRAM, ...)' succeeds"];
  start -> new_stream_socket [label="when 'socket(..., SOCK_STREAM, ...)' succeeds"];
  start -> new_unknown_socket [label="when 'socket(..., ..., ...)' succeeds"];

  /* On "bind".  */
  start -> bound_unknown_socket [label="when 'bind(X, ...)' succeeds"];
  constant_fd -> bound_unknown_socket [label="when 'bind(X, ...)' succeeds"];
  new_stream_socket -> bound_stream_socket [label="when 'bind(X, ...)' succeeds"];
  new_datagram_socket -> bound_datagram_socket [label="when 'bind(X, ...)' succeeds"];
  new_unknown_socket -> bound_unknown_socket [label="when 'bind(X, ...)' succeeds"];

  /* On "listen".  */
  start -> listening_stream_socket [label="when 'listen(X, ...)' succeeds"];
  bound_stream_socket -> listening_stream_socket [label="when 'listen(X, ...)' succeeds"];
  bound_unknown_socket -> listening_stream_socket [label="when 'listen(X, ...)' succeeds"];

  /* On "accept".  */
  start -> connected_stream_socket [label="when 'accept(OTHER, ...)' succeeds on a listening_stream_socket"];
  constant_fd -> connected_stream_socket [label="when 'accept(OTHER, ...)' succeeds on a listening_stream_socket"];

  /* On "connect".  */
  new_stream_socket -> connected_stream_socket [label="when 'connect(X, ...)' succeeds"];
  new_datagram_socket -> new_datagram_socket [label="when 'connect(X, ...)' succeeds"];
  new_unknown_socket -> stop [label="when 'connect(X, ...)' succeeds"];
  start -> stop [label="when 'connect(X, ...)' succeeds"];
  constant_fd -> stop [label="when 'connect(X, ...)' succeeds"];

  /* on_condition.  */
  unchecked_read_write -> valid_read_write [label="on 'X >= 0'"];
  unchecked_read_only -> valid_read_only [label="on 'X >= 0'"];
  unchecked_write_only -> valid_write_only [label="on 'X >= 0'"];
  unchecked_read_write -> invalid [label="on 'X < 0'"];
  unchecked_read_only -> invalid [label="on 'X < 0'"];
  unchecked_write_only -> invalid [label="on 'X < 0'"];

  /* Leaks.  */
  unchecked_read_write -> stop [label="on leak:\nWarn('leak')"];
  unchecked_read_only -> stop [label="on leak:\nWarn('leak')"];
  unchecked_write_only -> stop [label="on leak:\nWarn('leak')"];
  valid_read_write -> stop [label="on leak:\nWarn('leak')"];
  valid_read_only -> stop [label="on leak:\nWarn('leak')"];
  valid_write_only -> stop [label="on leak:\nWarn('leak')"];
  new_datagram_socket -> stop [label="on leak:\nWarn('leak')"];
  new_stream_socket -> stop [label="on leak:\nWarn('leak')"];
  new_unknown_socket -> stop [label="on leak:\nWarn('leak')"];
  bound_datagram_socket -> stop [label="on leak:\nWarn('leak')"];
  bound_stream_socket -> stop [label="on leak:\nWarn('leak')"];
  bound_unknown_socket -> stop [label="on leak:\nWarn('leak')"];
  listening_stream_socket -> stop [label="on leak:\nWarn('leak')"];
  connected_stream_socket -> stop [label="on leak:\nWarn('leak')"];
}
